<?php
namespace app\common\library;
/**
 * 使用Goutte库递归采集页面内容中的email地址的类
 * 注意：此代码需要先通过composer安装Goutte库: composer require fabpot/goutte
 */
use Goutte\Client;
class Crawl {
     /**
     * 单例对象
     */
    protected static $instance;
    /**
     * 已访问的URL集合
     * @var array
     */
    private $visited = [];

    /**
     * 已采集的email集合
     * @var array
     */
    private $emails = [];

    /**
     * Goutte客户端实例
     * @var \Goutte\Client
     */
    private $client;

    /**
     * Crawl constructor.
     */
    public function __construct() {
        // 创建Goutte客户端
        $this->client = new Client();
        $this->visited = [];
        $this->emails = [];
    }
    /**
     * 初始化
     * @access public
     * @param array $options 参数
     * @return Email
     */
    public static function instance($options = [])
    {
        if (is_null(self::$instance)) {
            self::$instance = new static($options);
        }

        return self::$instance;
    }
    /**
     * 递归采集页面内容中的email地址
     *
     * @param string $url 起始URL
     * @param int $depth 递归深度
     * @return array 采集到的email地址数组
     */
    public function crawlEmails($url, $depth = 2) {
        // 重置状态
        $this->visited = [];
        $this->emails = [];
        $depth = intval($depth) <= 0 ? 1 : intval($depth);
        // 开始递归爬取
        $this->doCrawl($url, $depth);
        return $this->emails;
    }

    /**
     * 实际执行递归爬取的私有方法
     *
     * @param string $url 当前URL
     * @param int $depth 剩余递归深度
     */
    private function doCrawl($url, $depth) {
        // 检查是否已达到递归深度或已访问过该URL
        if ($depth <= 0 || in_array($url, $this->visited)) {
            return;
        }
        
        // 标记为已访问
        $this->visited[] = $url;

        try {
            // 发送请求并获取响应
            $crawler = $this->client->request('GET', $url);
            // 提取页面 中的所有文本
            $text = $crawler->text();
            // 使用正则表达式匹配email地址
            preg_match_all('/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/', $text, $matches);

            // 添加匹配到的email地址
            foreach ($matches[0] as $email) {
                if (!in_array($email, $this->emails)) {
                    $this->emails[] = $email;
                }
            }

            // 如果还有递归深度，则继续爬取页面中的链接
            if ($depth > 1) {
                // 提取页面中的所有链接
                $links = $crawler->filter('a')->extract(['href']);
                // 遍历链接
                foreach ($links as $link) {
                    if(empty($link)){  // 空的
                        continue;
                    }
                    // 确保链接是有效的
                    $absoluteUrl = $this->makeAbsoluteUrl($link, $url);
                    if(!empty($absoluteUrl)){
                        $absoluteUrl = str_replace("\\","",$absoluteUrl);
                        if (!empty($absoluteUrl) && $this->isSameDomain($url,$absoluteUrl)) {
                            // 递归爬取
                            try{
                                $this->doCrawl($absoluteUrl, $depth - 1);
                            }catch (\Exception $e) {}
                        }
                    }

                }
            }
        } catch (\Exception $e) {
            // 忽略错误并继续
            #cho '爬取错误: ' . $e->getMessage();
            error_log('爬取错误: ' . $e->getMessage());
        }
    }

    /**
     * 将相对URL转换为绝对URL
     *
     * @param string $relativeUrl 相对URL
     * @param string $baseUrl 基础URL
     * @return string|null 绝对URL或null(如果无效)
     */
    private function makeAbsoluteUrl($relativeUrl, $baseUrl) {
        // 忽略javascript链接和锚点
        if (strpos($relativeUrl, 'javascript:') === 0 || strpos($relativeUrl, '#') === 0) {
            return null;
        }

        // 如果已经是绝对URL，则直接返回
        if (filter_var($relativeUrl, FILTER_VALIDATE_URL)) {
            return $relativeUrl;
        }

        // 解析基础URL
        $baseParts = parse_url($baseUrl);
        if (!isset($baseParts['scheme']) || !isset($baseParts['host'])) {
            return null;
        }

        // 构建绝对URL
        $absoluteUrl = $baseParts['scheme'] . '://' . $baseParts['host'];

        if (isset($baseParts['port'])) {
            $absoluteUrl .= ':' . $baseParts['port'];
        }

        if (isset($baseParts['path'])) {
            // 获取基础路径的目录部分
            $basePath = dirname($baseParts['path']);
            if ($basePath !== '/') {
                $absoluteUrl .= $basePath;
            }
        }

        // 添加相对路径
        if ($relativeUrl[0] !== '/') {
            $absoluteUrl .= '/';
        }
        $absoluteUrl .= $relativeUrl;

        // 移除重复的斜杠
        $absoluteUrl = preg_replace('#//+#', '/', $absoluteUrl);
        // 修复协议后的斜杠
        $absoluteUrl = preg_replace('#^(https?):/#', '$1://', $absoluteUrl);

        return $absoluteUrl;
    }

    /**
     * 检查两个URL是否属于同一个域名
     *
     * @param string $url1 第一个URL
     * @param string $url2 第二个URL
     * @return bool 是否属于同一个域名
     */
    private function isSameDomain($url1, $url2) {
        $host1 = parse_url($url1, PHP_URL_HOST);
        $host2 = parse_url($url2, PHP_URL_HOST);
        return $host1 === $host2;
    }
}

// 使用示例
// 注意：使用前需安装Goutte库: composer require fabpot/goutte
/*
require 'vendor/autoload.php';

$crawler = new Crawl();
$startUrl = 'https://example.com';
$emails = $crawler->crawlEmails($startUrl, 2);

echo "找到的email地址:\n";
foreach ($emails as $email) {
    echo $email . "\n";
}
*/

?>